﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Husb.Validation;

namespace Husb.Framework
{
    [Serializable]
    public abstract class EntityBase : IEntity, IEditableObject, INotifyPropertyChanged, INotifyPropertyChanging, IDataErrorInfo
    {
        #region IDataErrorInfo 成员

        public string Error
        {
            //get { return ValidationMessage; }
            get
            {
                if (!this.IsValid)
                {
                    return this.ValidationRules.GetBrokenRules().ToString(RuleSeverity.Error);
                }
                return string.Empty;
            }

        }

        public string this[string propertyName]
        {
            //get 
            //{
            //    if (_brokenRules.ContainsKey(propertyName))
            //    {
            //        return _brokenRules[propertyName];
            //    }
            //    return "";
            //}
            get
            {
                string result = string.Empty;
                if (!this.IsValid)
                {
                    BrokenRule rule = this.ValidationRules.GetBrokenRules().GetFirstBrokenRule(propertyName);
                    if (rule != null)
                    {
                        result = rule.Description;
                    }
                }
                return result;
            }

        }

        #endregion

        #region INotifyPropertyChanging 成员

        //public event PropertyChangingEventHandler PropertyChanging;
        [NonSerialized()]
        private PropertyChangingEventHandler _nonSerializableChangingHandlers;
        private PropertyChangingEventHandler _serializableChangingHandlers;

        /// <summary>
        /// Implements a serialization-safe PropertyChanging event.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design","CA1062:ValidateArgumentsOfPublicMethods")]
        public event PropertyChangingEventHandler PropertyChanging
        {
            add
            {
                if (value.Method.IsPublic &&(value.Method.DeclaringType.IsSerializable || value.Method.IsStatic))
                    _serializableChangingHandlers = (PropertyChangingEventHandler)System.Delegate.Combine(_serializableChangingHandlers, value);
                else
                    _nonSerializableChangingHandlers = (PropertyChangingEventHandler)System.Delegate.Combine(_nonSerializableChangingHandlers, value);
            }
            remove
            {
                if (value.Method.IsPublic && (value.Method.DeclaringType.IsSerializable || value.Method.IsStatic))
                    _serializableChangingHandlers = (PropertyChangingEventHandler)System.Delegate.Remove(_serializableChangingHandlers, value);
                else
                    _nonSerializableChangingHandlers = (PropertyChangingEventHandler)System.Delegate.Remove(_nonSerializableChangingHandlers, value);
            }
        }

        /// <summary>
        /// Call this method to raise the PropertyChanging event
        /// for all object properties.
        /// </summary>
        /// <remarks>
        /// This method is for backward compatibility with
        /// CSLA .NET 1.x.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnIsDirtyChanging()
        {
            OnUnknownPropertyChanging();
        }

        /// <summary>
        /// Call this method to raise the PropertyChanging event
        /// for all object properties.
        /// </summary>
        /// <remarks>
        /// This method is automatically called by MarkDirty. It
        /// actually raises PropertyChanging for an empty string,
        /// which tells data binding to refresh all properties.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnUnknownPropertyChanging()
        {
            OnPropertyChanging(string.Empty);
        }

        /// <summary>
        /// Call this method to raise the PropertyChanging event
        /// for a specific property.
        /// </summary>
        /// <param name="propertyName">Name of the property that
        /// has Changing.</param>
        /// <remarks>
        /// This method may be called by properties in the business
        /// class to indicate the change in a specific property.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnPropertyChanging(string propertyName)
        {
            if (_nonSerializableChangingHandlers != null)
                _nonSerializableChangingHandlers.Invoke(this, new PropertyChangingEventArgs(propertyName));
            if (_serializableChangingHandlers != null)
                _serializableChangingHandlers.Invoke(this, new PropertyChangingEventArgs(propertyName));
        }

        #endregion

        #region INotifyPropertyChanged 成员

        //public event PropertyChangedEventHandler PropertyChanged;
        [NonSerialized()]
        private PropertyChangedEventHandler _nonSerializableChangedHandlers;
        private PropertyChangedEventHandler _serializableChangedHandlers;

        /// <summary>
        /// Implements a serialization-safe PropertyChanged event.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
          "CA1062:ValidateArgumentsOfPublicMethods")]
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                if (value.Method.IsPublic &&
                   (value.Method.DeclaringType.IsSerializable ||
                    value.Method.IsStatic))
                    _serializableChangedHandlers = (PropertyChangedEventHandler)
                      System.Delegate.Combine(_serializableChangedHandlers, value);
                else
                    _nonSerializableChangedHandlers = (PropertyChangedEventHandler)
                      System.Delegate.Combine(_nonSerializableChangedHandlers, value);
            }
            remove
            {
                if (value.Method.IsPublic &&
                   (value.Method.DeclaringType.IsSerializable ||
                    value.Method.IsStatic))
                    _serializableChangedHandlers = (PropertyChangedEventHandler)
                      System.Delegate.Remove(_serializableChangedHandlers, value);
                else
                    _nonSerializableChangedHandlers = (PropertyChangedEventHandler)
                      System.Delegate.Remove(_nonSerializableChangedHandlers, value);
            }
        }

        /// <summary>
        /// Call this method to raise the PropertyChanged event
        /// for all object properties.
        /// </summary>
        /// <remarks>
        /// This method is for backward compatibility with
        /// CSLA .NET 1.x.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnIsDirtyChanged()
        {
            OnUnknownPropertyChanged();
        }

        /// <summary>
        /// Call this method to raise the PropertyChanged event
        /// for all object properties.
        /// </summary>
        /// <remarks>
        /// This method is automatically called by MarkDirty. It
        /// actually raises PropertyChanged for an empty string,
        /// which tells data binding to refresh all properties.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnUnknownPropertyChanged()
        {
            OnPropertyChanged(string.Empty);
        }

        /// <summary>
        /// Call this method to raise the PropertyChanged event
        /// for a specific property.
        /// </summary>
        /// <param name="propertyName">Name of the property that
        /// has changed.</param>
        /// <remarks>
        /// This method may be called by properties in the business
        /// class to indicate the change in a specific property.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (_nonSerializableChangedHandlers != null)
                _nonSerializableChangedHandlers.Invoke(this,
                  new PropertyChangedEventArgs(propertyName));
            if (_serializableChangedHandlers != null)
                _serializableChangedHandlers.Invoke(this,
                  new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region IEditableObject 成员

        public void BeginEdit()
        {
            if (!reserved)
            {
                reserved = true;
                Backup();
                
            }
            //throw new NotImplementedException();
        }

        public void CancelEdit()
        {
            if (reserved)
            {
                Restore();
                reserved = false;
            }
        }

        public void EndEdit()
        {
            if (reserved)
            {
                if (_stateStack != null)
                {
                    _stateStack.Clear();
                    _stateStack = null;
                }
                reserved = false;
            }
        }

        #endregion

        #region 实现 IEditableObject 的辅助成员

        [NonSerialized]
        private bool reserved;

        [NotUndoable]
        private Stack<byte[]> _stateStack = null;

        /// <summary>
        /// 备份
        /// </summary>
        protected virtual void Backup()
        {
            if (_stateStack == null)
            {
                _stateStack = new Stack<byte[]>();
            }


            Type currentType = this.GetType();
            HybridDictionary state = new HybridDictionary();

            //WriteLog(currentType.Name + "================================");

            FieldInfo[] fields;
            do
            {
                fields = currentType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
                foreach (FieldInfo field in fields)
                {
                    #region
                    //  不需要恢复的字段，就不用保存
                    if (NotUndoableField(field))
                        continue;

                    // 如果不是在本类型声明的，就不管，只管本类型声明的属性
                    if (field.DeclaringType != currentType)
                        continue;

                    object value = field.GetValue(this);
                    // 如果是同一类型
                    if (typeof(EntityBase).IsAssignableFrom(field.FieldType))
                    {
                        // 基类是 EntityBase 的字段
                        if (value == null)
                        {
                            // 即使是null值也要保存
                            state.Add(GetFieldName(field), value);
                            //WriteLog("EntityBase字段：" + currentType.Name  +" -- " + GetFieldName(field) + " :  null");
                        }
                        else
                        {
                            // 
                            ((EntityBase)value).Backup();
                        }
                    }
                    else if (typeof(ICollection).IsAssignableFrom(field.FieldType) && value != null && ((ICollection)value).Count > 0)
                    {
                        // 基类是EntityBase 的集合字段。
                        var col = (ICollection)value;
                        foreach (var item in col)
                        {
                            if (item is EntityBase)
                                ((EntityBase)item).Backup();
                        }

                        // 
                        state.Add(GetFieldName(field), value);
                        //WriteLog("集合：" + currentType.Name + " -- " + GetFieldName(field) + " : " + (value == null ? "null" : value.ToString()));
                    }
                    else
                    {
                        // 这里是当前类声明的字段
                        state.Add(GetFieldName(field), value);

                        //WriteLog("当前类：" + currentType.Name +" -- " + GetFieldName(field) + " : " + (value == null ? "null" : value.ToString()));
                    }

                    #endregion
                }

                currentType = currentType.BaseType;
            } while (currentType != null);

            using (MemoryStream buffer = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(buffer, state);

                // 将stream转换成byte[]后，压入栈中
                _stateStack.Push(buffer.ToArray());
            }
        }

        /// <summary>
        /// 恢复
        /// </summary>
        protected virtual void Restore()
        {
            if (_stateStack != null && _stateStack.Count > 0)
            {
                #region 

                // 先反序列化，得到字典。
                HybridDictionary state;
                using (MemoryStream buffer = new MemoryStream(_stateStack.Pop()))
                {
                    buffer.Position = 0;
                    BinaryFormatter formatter = new BinaryFormatter();
                    state = (HybridDictionary)formatter.Deserialize(buffer);
                }

                Type currentType = this.GetType();
                FieldInfo[] fields;

                do
                {
                    //获取字段信息。
                    fields = currentType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

                    #region

                    foreach (FieldInfo field in fields)
                    {
                        //  仅仅处理本类型声明的字段，不管基类的字段
                        if (field.DeclaringType == currentType)
                        {
                            // 仅仅处理需要保持的字段，因为这些字段才保存了，才能恢复。
                            if (!NotUndoableField(field))
                            {
                                // 得到最新状态下，本对象的值
                                object value = field.GetValue(this);
                                if (typeof(EntityBase).IsAssignableFrom(field.FieldType))
                                {
                                    // 这是一个子对象，看看其原来的值是否已放到状态字典中了
                                    if (state.Contains(GetFieldName(field)))
                                    {
                                        // 放进字典中的肯能原来为 null，所以这里赋值为null
                                        field.SetValue(this, null);
                                    }
                                    else
                                    {
                                        if (value != null)
                                        {
                                            // 子对象调用Restore方法。
                                            ((EntityBase)value).Restore();
                                        }
                                    }
                                }
                                else
                                {
                                    field.SetValue(this, state[GetFieldName(field)]);
                                }

                                //检查值是否为集合，如果是，就对每项调用Restore方法。
                                var oldValue = state[GetFieldName(field)];
                                if (oldValue != null && oldValue is ICollection)
                                {
                                    var col = (ICollection)oldValue;
                                    foreach (var item in col)
                                    {
                                        if (item is EntityBase)
                                            ((EntityBase)item).Restore();
                                    }
                                }

                            } // 结束仅处理需要恢复的字段
                        } // 结束仅处理类型字段
                    }// 结束字段遍历

                    #endregion

                    currentType = currentType.BaseType;
                } while (currentType != null);

                #endregion
            }
           
        }

        private static string GetFieldName(FieldInfo field)
        {
            return field.DeclaringType.Name + "!" + field.Name;
        }
        private static bool NotUndoableField(FieldInfo field)
        {
            return Attribute.IsDefined(field, typeof(NotUndoableAttribute));
        }
        #endregion

        #region 验证

        private ValidationRules _validationRules;
        protected Validation.ValidationRules ValidationRules
        {
            get
            {
                if (_validationRules == null)
                {
                    _validationRules = new Husb.Validation.ValidationRules(this);
                    //_validationRules.ValidatingRules.CollectionChanged += new NotifyCollectionChangedEventHandler(ValidatingRules_CollectionChanged);
                }
                else if (_validationRules.Target == null)
                    _validationRules.SetTarget(this);
                return _validationRules;
            }
        }

        protected virtual void AddValidationRules()
        {
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public virtual Validation.BrokenRulesCollection BrokenRulesCollection
        {
            get { return ValidationRules.GetBrokenRules(); }
        }


        private StringDictionary _brokenRules = new StringDictionary();
        protected void AddRule(string propertyName, string errorMessage, bool isBroken)
        {
            if (isBroken)
            {
                _brokenRules[propertyName] = errorMessage;
            }
            else
            {
                if (_brokenRules.ContainsKey(propertyName))
                {
                    _brokenRules.Remove(propertyName);
                }
            }
        }

        /// <summary>
        /// 子类要实现此方法，添加类似这样的代码
        /// <code>AddRule("Price", "Price must be greater than or equal to 0", this.Price < 0);</code>
        /// <code>AddRule("Name", "Name must be set", string.IsNullOrEmpty(this.Name));</code>
        /// </summary>
        protected abstract void Validate();

        /// <summary>
        /// 客户端调用此方法，来实现验证
        /// </summary>
        public bool IsValid
        {
            get
            {
                return ValidationRules.IsValid;

                //Validate();
                //return this._brokenRules.Count == 0;
            }
        }

        /// <summary>
        /// 如果验证失败，可以从这里读取验证信息
        /// </summary>
        public string ValidationMessage
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                foreach (string messages in this._brokenRules.Values)
                {
                    sb.AppendLine(messages);
                }
                return sb.ToString();
            }
        }

        #endregion

        public abstract string FilterExpression
        {
            get;
        }

        public static void WriteLog(string logMessage)
        {
            //string logFileName = GetLogFileName();
            string logFileName = "s.log";// GetLogFileName();
            using (StreamWriter w = File.AppendText(logFileName))
            {
                w.WriteLine(logMessage);
                w.Flush();
                w.Close();
            }
        }

        public static string GetLogFileName()
        {
            string logDirectory = @"e:\\log\"; //Application.StartupPath + LogFolder;
            if (!Directory.Exists(logDirectory))
            {
                Directory.CreateDirectory(logDirectory);
            }
            string logFile = logDirectory + DateTime.Now.ToString("yyyyMMdd") + ".txt";
            if (!File.Exists(logFile))
            {
                FileStream log = File.Create(logFile);
                log.Close();
            }
            return logFile;
        }

    }
}
